//////////////////////////////////////////////////////////////////////////
//
// This file is a part of UKROP project.
// Copyright (c) Vladimir Gumenuk
//
// PROJECT:         Xort Custom Debug Engine
// FILE:            Program.cpp : Implementation of CProgram
// AUTHOR:          Vladimir Gumenuk
// DATE:            Dec-2012 -- Jan-2013
// MODIFICATIONS:
//
//////////////////////////////////////////////////////////////////////////
 
#include "stdafx.h"
#include "xort.h"
#include "xruntime.h"
#include "xbuildin.h"
#include "xcode.h"
#include "xobject.h"
#include "DebugProperty.h"
#include "DebugStack.h"
#include "StackFrame.h" 
#include "EventBase.h" 
#include "Breakpoint.h" 
#include "Context.h"
#include "Document.h"
#include "Program.h"
#include "dllmain.h"
#include "DebugProperty.h"


#define __LOCK() CAutoLock __lock( &m_CS )

// CProgram
CProgram::CProgram()
{
    ATLTRACE( "CProgram::CProgram()\n" );
    m_hRuntimeThread   = NULL;
    m_dwThreadID       = 0;
    m_pCurProc         = NULL;
    m_NextStepKind     = STEP_INTO;
    m_StepUnit         = STEP_STATEMENT;
    m_bStopFlag        = FALSE;
    m_iArgc            = 0;
    m_pArgv            = NULL;
    setState( XVM_Idle );
    m_CS.Init();
    xInitPrims();
}

CProgram::~CProgram()
{
    xFreePrims();
    m_dbgStack.clear();
    m_CS.Term();
    ATLTRACE( "CProgram::~CProgram()\n" );
}

HRESULT CProgram::FinalConstruct()
{
    ATLTRACE( "CProgram::FinalConstruct() -> ENTER\n" );
    m_hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
    ResetEvent( m_hEvent );

    if( m_hRuntimeThread = CreateThread( NULL, 0, RuntimeEntryStatic, this, 0, &m_dwThreadID ) )
    {
        ATLTRACE( "CProgram::FinalConstruct() -> WAITING for internal thread: %d\n", m_dwThreadID );
        WaitForSingleObject( m_hEvent, INFINITE );
    } else {
        ATLTRACE( "CProgram::FinalConstruct() -> CreateThread failed: %d\n", GetLastError() );
    }

    ATLTRACE( "CProgram::FinalConstruct() -> LEAVE\n" );
    return S_OK;
}

void CProgram::FinalRelease()
{
    ATLTRACE( "CProgram::FinalRelease() -> ENTER\n" );
    if( m_hRuntimeThread )
    {
        setState( XVM_Terminating );
        SetEvent( m_hEvent );
        WaitForSingleObject( m_hRuntimeThread, INFINITE );
        CloseHandle( m_hRuntimeThread );
        m_hRuntimeThread = NULL;
        m_dwThreadID = 0;
    }

    if( m_hEvent )
    {
        CloseHandle( m_hEvent );
        m_hEvent = NULL;
    }

    m_iArgc = 0;
    if( m_pArgv )
    {
        free( m_pArgv );
        m_pArgv = NULL;
    }

    m_dbgStack.ReleaseInternalXrefs();
    m_dbgStack.clear();
    ATLTRACE( "CProgram::FinalRelease() -> LEAVE\n" );
}

STDMETHODIMP CProgram::Load( LPCOLESTR lpszXRTFile )
{
    if( !lpszXRTFile )
        return E_POINTER;
    if( !m_hRuntimeThread || !m_hEvent )
        return E_UNEXPECTED;

    USES_CONVERSION;
    m_csFullCmdLine = OLE2A( lpszXRTFile );

    int iRet = ParseCmdLine( m_csFullCmdLine.GetBuffer(), &m_iArgc, &m_pArgv );
    if( iRet != 0 )
        return E_FAIL;
    
    m_csXrtFileName = m_pArgv[0];

    iRet = LoadExecutable( m_csXrtFileName, m_listGlobal, m_litPool );
    if( iRet == XORT_OK )
    {
        SymbolRef mainEntry = FindSymbol( m_listGlobal, "main", TRUE );
        if( mainEntry.isNULL() )
        {
            iRet = ERR_ENTRYPOINT_NOT_FOUND;
        } else {
            if( ( iRet = Initialize( m_listGlobal, m_litPool, this ) ) == XORT_OK )
            {
                string *pcsSourceFile = m_litPool.get( 2 ); // literal pool: 0-1 - compile info, 2 - source file
                ATLTRACE( "Source: %s\n", pcsSourceFile->GetBuffer() );

                m_pCurProc = &mainEntry; // set the entry point symbol

                // create the document that represents our source file
                if( SUCCEEDED( CDocument::Create( &m_spSourceFile ) ) )
                {
                    CComQIPtr<IXDocument> spXDoc( m_spSourceFile );
                    if( FAILED( spXDoc->Initialize( pcsSourceFile->GetBuffer() ) ) )
                        iRet = ERR_ENTRYPOINT_NOT_FOUND;
                }
            }
        }
    }

    HRESULT hr = ( iRet == XORT_OK ) ? S_OK : E_FAIL;

    ATLTRACE( "CProgram::Load() -> %S, hr: 0x%08X\n", lpszXRTFile, hr );
    return hr;
}

DWORD __stdcall CProgram::RuntimeEntryStatic( LPVOID pParam )
{
    CProgram *pProgram = (CProgram *)pParam;
    return pProgram->RuntimeRun();
}

DWORD CProgram::RuntimeRun()
{
    ATLTRACE( "CProgram::RuntimeRun() -> ENTER\n" );
    int iRet = XORT_OK;

    CoInitialize( NULL );
    SetEvent( m_hEvent );

    while( getState() != XVM_Terminating )
    {
        switch( getState() )
        {
        case XVM_Idle: 
            Sleep( 100 ); // :)
            break;

        case XVM_Executing:
            {
                Sleep( 100 ); // wait IDE for complete switch to debug mode :)
                ATLTRACE( "RUNTIME -> ENTER XVM!\n" );

                try
                {
                    SymbolRef mainEntry = FindSymbol( m_listGlobal, "main", TRUE );
                    iRet = Run( mainEntry, m_iArgc, m_pArgv );
                    
                } catch ( int iError ) {
                    ATLTRACE( "RUNTIME -> user terination request: %d\n", iError );
                }

                ATLTRACE( "RUNTIME -> LEAVE XVM!\n" );

                if( getState() != XVM_Terminating ) {
                    setState( XVM_Idle ); // do nothing after that
                    _AtlModule.PostMsgToMainThread( WM_PROGRAM_ENDED );    
                }
                break;
            }

        default:
            break;
        }
    }

    SetEvent( m_hEvent );
    CoUninitialize();
    CloseHandle( m_hRuntimeThread );
    m_hRuntimeThread = NULL;
    m_dwThreadID = 0;

    ATLTRACE( "CProgram::RuntimeRun() -> EXIT\n" );
    return 0;
}

HRESULT CProgram::EngineAttach( IDebugProgram2* pMDMProgram, IDebugEngine2 *pEngine, IDebugProcess2 *pProcess, IDebugEventCallback2* pCallback, DWORD dwReason )
{
    //ATLTRACE( "CProgram::EngineAttach() -> reason: %d\n", dwReason );
    GUID test;
    pMDMProgram->GetProgramId( &test );
    if( test != m_guidProgId ) // guid will be typically the same!
    {
        m_guidProgId = test;
    }

    CProgramCreateEvent *pProgCreate = new CProgramCreateEvent(); // send event that program is loaded but not started yet
    pProgCreate->Send();

    CLoadCompleteEvent *pLDComplete = new CLoadCompleteEvent(); // tell SDM that program is loaded
    pLDComplete->Send();

    string csFmt;
    csFmt.Format( "Loaded: '%s'...\nCMD line: %s\n", m_csXrtFileName.GetBuffer(), m_csFullCmdLine.GetBuffer() );
    COutputStringEvent *pMsgEvent = new COutputStringEvent( csFmt );
    pMsgEvent->Send();

    CActivateDocumentEvent *pActDocEvent = new CActivateDocumentEvent( m_spSourceFile );
    pActDocEvent->Send();

    CEntryPointEvent *pEntryPt = new CEntryPointEvent();        // tell SDM that entry point is reached :)
    pEntryPt->Send();

    //ATLTRACE( "CProgram::EngineAttach() -> LEAVE, OK\n" );
    Execute(); // and start executing the program!
    return S_OK;
}

////////////////////////////////////////////////////////////////////////////// 
// IDebugProgramNode2 methods 
STDMETHODIMP CProgram::GetProgramName( BSTR* pbstrProgramName ) 
{
    //ATLTRACE( "CProgram::GetProgramName()\n" );
    if( !pbstrProgramName )
        return E_POINTER;

    char  szDrive[_MAX_DRIVE];  
    char  szDir[_MAX_DIR];  
    char  szName[_MAX_FNAME];  
    char  szExt[_MAX_EXT];  
    char  szFilename[_MAX_FNAME + _MAX_EXT];  

    _splitpath( m_csXrtFileName.GetBuffer(), szDrive, szDir, szName, szExt );  
    strcpy( szFilename, szName );  
    strcat( szFilename, szExt );  

    *pbstrProgramName = CComBSTR( szFilename ).Detach();
    return S_OK;
}

STDMETHODIMP CProgram::GetHostName( DWORD dwHostNameType, BSTR* pbstrHostName ) 
{
    if( !pbstrHostName )
        return E_POINTER;
    *pbstrHostName = SysAllocString( L"XortDebugEngine.exe" );  
    return S_OK;
}

STDMETHODIMP CProgram::GetHostMachineName_V7( BSTR* pbstrHostMachineName ) 
{
    ATLTRACE( "CProgram::GetHostMachineName_V7()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::DetachDebugger_V7() 
{
    ATLTRACE( "CProgram::DetachDebugger_V7()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::Attach_V7( IDebugProgram2* pMDMProgram, IDebugEventCallback2* pCallback, DWORD dwReason ) 
{
    ATLTRACE( "CProgram::Attach_V7() -> reason: %d\n", dwReason );
    return E_NOTIMPL;
} 

STDMETHODIMP CProgram::GetEngineInfo( BSTR* pbstrEngine, GUID* pguidEngine ) 
{ 
    if( pbstrEngine ) 
        *pbstrEngine = SysAllocString( L"XortDebugEngine" ); 

    if( pguidEngine ) 
        *pguidEngine = __uuidof(Engine); 

    return S_OK; 
} 

STDMETHODIMP CProgram::GetHostPid( AD_PROCESS_ID *pHostProcessId ) 
{ 
    if( !pHostProcessId )
        return E_POINTER;

    return _AtlModule.getProcessNoAddRef()->GetPhysicalProcessId( pHostProcessId );
} 

STDMETHODIMP CProgram::CreatePendingBreakpoint( IDebugBreakpointRequest2 *pBPRequest, IDebugPendingBreakpoint2 **ppPendingBP ) 
{ 
    if( !pBPRequest || !ppPendingBP )
        return E_INVALIDARG;

    CComObject<CPendingBreakpoint> *pPending; 
    HRESULT hr = CComObject<CPendingBreakpoint>::CreateInstance( &pPending ); 
    if( SUCCEEDED( hr ) )
    {
        pPending->AddRef(); 
        if( SUCCEEDED( hr = pPending->Initialize( pBPRequest ) ) )
        {
            hr = pPending->QueryInterface( ppPendingBP ); 
        }
        pPending->Release(); 
    }

    // we do not store neither manage pending breakpoints, let the IDE to perform this...
    return hr; 
} 

// returns F_FAIL if not found
STDMETHODIMP CProgram::FindSourceFile( LPCOLESTR lpszFileName, IDebugDocument2 **ppDoc )
{
    // since we support only 1 file for now...
    if( m_spSourceFile )
    {
        CComBSTR bsFileName;
        m_spSourceFile->GetName( GN_FILENAME, &bsFileName );

        ATLTRACE( "CProgram::FindSourceFile() -> '%S' <?> '%S'\n", lpszFileName, bsFileName );

        if( bsFileName == lpszFileName )
        {
            *ppDoc = m_spSourceFile;
            (*ppDoc)->AddRef();
            return S_OK;
        }
    }

    return E_FAIL;
}

STDMETHODIMP CProgram::GetGlobalSymbolTable( IUnknown **ppOut )
{
    if( ppOut )
        *ppOut = (IUnknown*)( &m_listGlobal );
    return S_OK;
}

STDMETHODIMP CProgram::CanStop( BOOL bStop )
{
    ATLTRACE( "CProgram::CanStop() -> %d\n", bStop );
    m_bStopFlag = bStop;
    return S_OK;
}

STDMETHODIMP CProgram::GetCurrentIP( DWORD *ppOut )
{
    *ppOut = get_IP();
    return S_OK;
}

STDMETHODIMP CProgram::GetDocument( IDebugDocument2 **ppOut )
{
    *ppOut = m_spSourceFile;
    (*ppOut)->AddRef();
    return S_OK;
}

STDMETHODIMP CProgram::SignalEvent()
{
    SetEvent( m_hEvent );
    return S_OK;
}

STDMETHODIMP CProgram::GetXRuntime( IUnknown **ppOut ) // actually XRuntime pointer with noAddRef is returned
{
    *ppOut = (IUnknown *)( static_cast<XRuntime*>( this ) );
    return S_OK;
}

//////////////////////////////////////////////////////////////////////////
// IDebugProgramNodeAttach2
STDMETHODIMP CProgram::OnAttach( __RPC__in REFGUID guidProgramId )
{
    m_guidProgId = guidProgramId;
    return S_OK;
}

////////////////////////////////////////////////////////////////////////////// 
// IDebugProgram2 methods 
STDMETHODIMP CProgram::EnumThreads( IEnumDebugThreads2** ppEnum ) 
{
    if( !ppEnum )
        return E_INVALIDARG;

    typedef CComEnumWithCount< IEnumDebugThreads2, &IID_IEnumDebugThreads2, IDebugThread2*, _CopyInterface<IDebugThread2> > CEnumDebugThreads; 
    CComObject<CEnumDebugThreads> *p;
    HRESULT hr = CComObject<CEnumDebugThreads>::CreateInstance( &p );
    if( SUCCEEDED( hr ) )
    {
        p->AddRef();

        CComQIPtr<IDebugThread2> spThread( this );
        IDebugThread2* pThreads[] = { spThread }; 
        p->Init( pThreads, &( pThreads[1] ), NULL, AtlFlagCopy ); 

        hr = p->QueryInterface( IID_IEnumDebugThreads2, (void**)ppEnum );
        p->Release();
    }

    //ATLTRACE( "CProgram::EnumThreads() -> LEAVE, hr: 0x%08X\n", hr );
    return hr;
}

STDMETHODIMP CProgram::GetName( BSTR* pbstrName ) 
{
    if( !pbstrName )
        return E_INVALIDARG;
    *pbstrName = SysAllocString( L"XortProgram" );
    return S_OK;
}

STDMETHODIMP CProgram::GetProcess( IDebugProcess2** ppProcess )
{
    return _AtlModule.getProcessNoAddRef()->QueryInterface( IID_IDebugProcess2, (void**)ppProcess );
}

STDMETHODIMP CProgram::Terminate() 
{
    ATLTRACE( "CProgram::Terminate()\n" );

    if( getState() == XVM_AtBreakpoint || getState() == XVM_DoSingleStep )
    {
        setState( XVM_Terminating );
        _AtlModule.PostMsgToMainThread( WM_CONTINUE_SYNC_EVENT ); // release main thread
        SetEvent( m_hEvent );

        CProgramDestroyEvent *pProgDestroy = new CProgramDestroyEvent( 0 );
        pProgDestroy->Send();

        ATLTRACE( "CProgram::Terminate() - Release breakpoint...\n" );
    }
    else
    if( m_hRuntimeThread )
    {
        ATLTRACE( "CProgram::Terminate() - call FinalRelease\n" );
        FinalRelease();

        CProgramDestroyEvent *pProgDestroy = new CProgramDestroyEvent( 0 );
        pProgDestroy->Send();
    }

    ATLTRACE( "CProgram::Terminate() - LEAVE, OK\n" );
    return S_OK;
}

STDMETHODIMP CProgram::Attach( IDebugEventCallback2* pCallback ) 
{
    ATLTRACE( "CProgram::Attach()\n" );
    return S_OK;
}

STDMETHODIMP CProgram::Detach() 
{
    ATLTRACE( "CProgram::Detach()\n" );
    return S_OK;
}

STDMETHODIMP CProgram::GetDebugProperty( IDebugProperty2** ppProperty ) 
{
    ATLTRACE( "CProgram::GetDebugProperty()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::CauseBreak() 
{
    ATLTRACE( "CProgram::CauseBreak()\n" );
    setState( XVM_CauseBreak );
    return S_OK;
}

STDMETHODIMP CProgram::EnumCodeContexts( IDebugDocumentPosition2* pDocPos, IEnumDebugCodeContexts2** ppEnum ) 
{
    ATLTRACE( "CProgram::EnumCodeContexts()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::GetMemoryBytes( IDebugMemoryBytes2** ppMemoryBytes ) 
{
    ATLTRACE( "CProgram::GetMemoryBytes()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::GetDisassemblyStream( DISASSEMBLY_STREAM_SCOPE dwScope, IDebugCodeContext2* pCodeContext, IDebugDisassemblyStream2** ppDisassemblyStream ) 
{
    ATLTRACE( "CProgram::GetDisassemblyStream()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::EnumModules( IEnumDebugModules2** ppEnum ) 
{
    ATLTRACE( "CProgram::EnumModules()\n" );
    return E_NOTIMPL; 
}

STDMETHODIMP CProgram::GetENCUpdate( IDebugENCUpdate** ppUpdate ) 
{
    ATLTRACE( "CProgram::GetENCUpdate()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::EnumCodePaths( LPCOLESTR pszHint, IDebugCodeContext2* pStart, IDebugStackFrame2* pFrame, BOOL fSource, IEnumCodePaths2** ppEnum, IDebugCodeContext2** ppSafety ) 
{
    ATLTRACE( "CProgram::EnumCodePaths()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::WriteDump( DUMPTYPE DumpType,LPCOLESTR pszCrashDumpUrl ) 
{
    ATLTRACE( "CProgram::WriteDump()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::CanDetach() 
{
    return S_OK; // always YES, why not? :)
} 

STDMETHODIMP CProgram::GetProgramId( GUID* pguidProgramId ) 
{ 
    ATLTRACE( "CProgram::GetProgramId()\n" );
    *pguidProgramId = m_guidProgId; 
    return S_OK; 
} 

STDMETHODIMP CProgram::Execute() 
{ 
    ATLTRACE( "CProgram::Execute()\n" );
    if( getState() != XVM_Terminating )
    {
        setState( XVM_Executing );
        _AtlModule.PostMsgToMainThread( WM_CONTINUE_SYNC_EVENT ); // release main thread first
        SetEvent( m_hEvent );
    } 

    ATLTRACE( "CProgram::Execute() -> LEAVE\n" );
    return S_OK; 
}

STDMETHODIMP CProgram::Continue( IDebugThread2 *pThread ) 
{ 
    ATLTRACE( "CProgram::Continue()\n" );
    HRESULT hr = S_OK;
    if( getState() == XVM_AtBreakpoint || getState() == XVM_DoSingleStep )
    {
        setState( XVM_Executing );
        _AtlModule.PostMsgToMainThread( WM_CONTINUE_SYNC_EVENT ); // release main thread first
        SetEvent( m_hEvent );
    } else
        hr = S_FALSE;

    ATLTRACE( "CProgram::Continue() -> LEAVE, hr: 0x%08X\n", hr );
    return hr; 
}

// Do not send a stopping event or an immediate (synchronous) event to IDebugEventCallback2::Event 
// while handling this call; otherwise the debugger may hang.
STDMETHODIMP CProgram::Step( IDebugThread2 *pThread, STEPKIND stepKind, STEPUNIT step ) 
{ 
    //ATLTRACE( "CProgram::Step(), sk: %d, step: %d\n", stepKind, step );
    if( stepKind == STEP_BACKWARDS )
        return E_NOTIMPL;

    HRESULT hr = S_OK;
    if( getState() == XVM_AtBreakpoint || getState() == XVM_DoSingleStep )
    {
        m_NextStepKind = stepKind;
        m_StepUnit = step;

        setState( XVM_DoSingleStep );
        _AtlModule.PostMsgToMainThread( WM_CONTINUE_SYNC_EVENT ); // release main thread first
        SetEvent( m_hEvent );
    } else
        hr = S_FALSE;

    //ATLTRACE( "CProgram::Step() -> LEAVE, hr: 0x%08X\n", hr );
    return hr; 
} 

////////////////////////////////////////////////////////////////////////////// 
//IDebugThread2 methods 
STDMETHODIMP CProgram::SetThreadName( LPCOLESTR pszName ) 
{
    ATLTRACE( "CProgram::SetThreadName()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::GetProgram( IDebugProgram2** ppProgram ) 
{
    if( !ppProgram )
        return E_INVALIDARG;
    return this->QueryInterface( IID_IDebugProgram2, (void**)ppProgram );
}

// The instruction pointer indicates the next instruction or statement to execute. 
// This method is used to retry a line of source code or to force execution to continue in another function, for example.
STDMETHODIMP CProgram::CanSetNextStatement( IDebugStackFrame2 *pStackFrame, IDebugCodeContext2 *pCodeContext ) 
{
    //ATLTRACE( "CProgram::CanSetNextStatement()\n" );
    if( !pCodeContext ) // pStackFrame can be NULL
        return E_INVALIDARG;
    if( getState() != XVM_AtBreakpoint && getState() != XVM_DoSingleStep )
        return E_FAIL;

    HRESULT hr = S_FALSE;
    dword dwLine = m_pCurProc->GetLineFromIP( get_IP() );
    if( dwLine != -1 )
    {
        hr = S_OK; // so, we have some statements to execute in the current function, and in the same stack frame context
        // TODO: check against stepKind: INTO, OVER, OUT
    }
    
    return hr;
}

STDMETHODIMP CProgram::SetNextStatement( IDebugStackFrame2 *pStackFrame, IDebugCodeContext2 *pCodeContext ) 
{
    ATLTRACE( "CProgram::SetNextStatement()\n" );
    if( !pCodeContext )
        return E_INVALIDARG;
    if( getState() != XVM_AtBreakpoint && getState() != XVM_DoSingleStep )
        return E_FAIL;
    // nice DO-nothing function :)
    return S_OK;
}

STDMETHODIMP CProgram::Suspend( DWORD *pdwSuspendCount ) 
{
    ATLTRACE( "CProgram::Suspend()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::Resume( DWORD *pdwSuspendCount ) 
{
    ATLTRACE( "CProgram::Resume()\n" );
    return E_NOTIMPL;
}

STDMETHODIMP CProgram::GetThreadProperties( THREADPROPERTY_FIELDS dwFields, THREADPROPERTIES *ptp ) 
{
    if( !ptp )
        return E_INVALIDARG;

    GetThreadId( &(ptp->dwThreadId) );
    GetName( &(ptp->bstrName) );
    ptp->bstrLocation = CComBSTR( m_pCurProc->m_csName.GetBuffer() ).Detach();
    ptp->dwSuspendCount = 1;

    if( getState() == XVM_AtBreakpoint || getState() == XVM_DoSingleStep )
        ptp->dwThreadState = THREADSTATE_STOPPED;
    else
        ptp->dwThreadState = THREADSTATE_RUNNING;
    ptp->bstrPriority = CComBSTR( L"Normal" );
    ptp->dwFields = TPF_ALLFIELDS;

    return S_OK;
}

STDMETHODIMP CProgram::GetLogicalThread( IDebugStackFrame2 *pStackFrame, IDebugLogicalThread2 **ppLogicalThread) 
{
    ATLTRACE( "CProgram::GetLogicalThread()\n" );
    return E_NOTIMPL;
} 

STDMETHODIMP CProgram::GetThreadId( DWORD* pdwThreadId ) 
{ 
    if( !pdwThreadId )
        return E_INVALIDARG;
    *pdwThreadId = m_dwThreadID; // AD_PROCESS_ID *pHostProcessId
    return S_OK;
} 

STDMETHODIMP CProgram::EnumFrameInfo( FRAMEINFO_FLAGS dwFieldSpec, UINT nRadix, IEnumDebugFrameInfo2 **ppEnum ) 
{
    if( !ppEnum )
        return E_INVALIDARG;
    //ATLTRACE( "CProgram::EnumFrameInfo() -> flags: 0x%08X\n", dwFieldSpec );
    if( ( getState() != XVM_AtBreakpoint && getState() != XVM_DoSingleStep ) || !m_pCurProc ) // this function is called when we are stopped at BP,
    {
        ATLTRACE( "CProgram::EnumFrameInfo() -> E_UNEXPECTED\n" );
        return E_UNEXPECTED;           // if not, and XVM is running, we can not create stack frames information 
    }

    HRESULT hr;
    
    CComPtr<IEnumDebugFrameInfo2> spEnumRet;
    if( FAILED( hr = CEnumFrameInfo::Create( &spEnumRet ) ) )
        return hr;

    CComQIPtr<IXEnumFrameInfo> spXEnum( spEnumRet ); // get our internal interface

    // Initialize enum
    DWORD dwNumFrames = m_dbgStack.count();
    for( DWORD i = 0; i < dwNumFrames; i++ ) // copy all stack frames
    {
        CDebugStackCellPtr pDbgCell = m_dbgStack.get( i );
        pDbgCell->FormatFunctionArgs( nRadix, m_listGlobal, this );
        spXEnum->AddFrameInfo( pDbgCell->m_pFrameInfo );
    }
    
    *ppEnum = spEnumRet.Detach();
    //ATLTRACE( "CProgram::EnumFrameInfo() -> LEAVE, hr: 0x%08X\n", hr );
    return S_OK;
} 

// called when XVM enters Exec method before any execution of first instruction of the given method
int CProgram::onEnterSystemFrame( XRuntime *pRT, SymbolRef CurrentSymbol )
{
    m_pCurProc = &CurrentSymbol; // set new procedure symbol

    // create procedure debug stack
    CFrameInfo *pFrameInfo = NULL;
    if( SUCCEEDED( BuildStackFrame( &pFrameInfo ) ) )
    {
        if( m_dbgStack.count() > 0 )
        {
            CComQIPtr<IXDocument> spXDoc( m_spSourceFile );
            TEXT_POSITION txtPos;
            spXDoc->GetPosition( &txtPos );

            CDebugStackCellPtr pTopCell = m_dbgStack.top();
            pTopCell->m_dwLastLineNo    = txtPos.dwLine;       // save last lineNo before entering new function
        }

        // save the information about current procedure
        CDebugStackCellPtr pCell = new CDebugStackCell( m_pCurProc, m_NextStepKind == STEP_INTO ? TRUE : FALSE, 0, pRT->get_StackBase(), pFrameInfo );
        m_dbgStack.push( pCell );

        pFrameInfo->Release();
    }

    return XORT_OK;
}

// called when XVM leaves Exec method
int CProgram::onLeaveSystemFrame( XRuntime *pRT, SymbolRef CurrentSymbol )
{
    m_dbgStack.pop(); // drop old frame
    if( m_dbgStack.count() > 0 )
    {
        CDebugStackCellPtr pCell = m_dbgStack.top();
        
        // restore old position in the document
        TEXT_POSITION txtPos;
        txtPos.dwLine   = pCell->m_dwLastLineNo;
        txtPos.dwColumn = 0;

        CComQIPtr<IXDocument> spXDoc( m_spSourceFile );
        spXDoc->SetPosition( &txtPos );
        
        // restore values for previous procedure and frame
        m_pCurProc = pCell->m_pProc;

    } else {// else this means that we are leaving main function...
        m_pCurProc = NULL;
    }
    return XORT_OK;
}

int CProgram::onEnterFrame( XRuntime *pRT )
{
    CDebugStackCellPtr pCell = m_dbgStack.top(); // there always should be something! if NULL - internal error

    CComQIPtr<IXStackFrame> spXStkFrame( pCell->m_pFrameInfo->m_pFrame );
    spXStkFrame->PushPropertyList(); // create new child frame
    UpdateStackFrame( spXStkFrame );

    return XORT_OK;
}

int CProgram::onLeaveFrame( XRuntime *pRT )
{
    CDebugStackCellPtr pCell = m_dbgStack.top(); // there always should be something! if NULL - internal error

    CComQIPtr<IXStackFrame> spXStkFrame( pCell->m_pFrameInfo->m_pFrame );
    spXStkFrame->PopPropertyList(); // delete child frame

    return XORT_OK;
}

//////////////////////////////////////////////////////////////////////////
// XVM debugger interface all invoked from internal thread where XVM is running...
int CProgram::beforeExecPrimitive( XRuntime *pRT, const DecoderData& primInfo )
{
    primInfo.m_strInstr.CopyToBuffer( m_szLastInstr );

    // update the position in the source file:
    TEXT_POSITION txtPos;
    txtPos.dwLine = m_pCurProc->GetLineFromIP( pRT->get_IP() );
    txtPos.dwColumn = 0;

    if( txtPos.dwLine != -1 ) 
    {
        CComQIPtr<IXDocument> spXDoc( m_spSourceFile );
        spXDoc->SetPosition( &txtPos );
    } // else do not update anything, leave it with old position.

    // Check if we are at possible breakpoint:
    IDebugBoundBreakpoint2 *pBP = (IDebugBoundBreakpoint2 *)m_pCurProc->GetBreakpoint( pRT->get_IP() );
    if( pBP )
    {
        BP_STATE bpState;
        pBP->GetState( &bpState );
        if( bpState == BPS_ENABLED )
        {
            CComQIPtr<IXBreakpoint> spXBP( pBP );
            TEXT_POSITION bpPos;
            spXBP->GetPosition( &bpPos );
            spXBP->IncrementHitCounter();
            ATLTRACE( "beforeExecPrimitive -> BP! at line: %d\n", bpPos.dwLine );

            setState( XVM_AtBreakpoint );

            ResetEvent( m_hEvent );
            _AtlModule.PostMsgToMainThread( WM_BREAKPOINT, (WPARAM)pBP );
            WaitForSingleObject( m_hEvent, INFINITE ); // wait for user action to continue execution
        }

    } else { // Check for possibility to do a single step. Normally txtPos.dwLine != -1 when the IP points to the 
             // beginning of statement.

        CDebugStackCellPtr pCurFrame = m_dbgStack.top();

        if( getState() == XVM_DoSingleStep && ( txtPos.dwLine != -1 ) && pCurFrame->m_bAllowSingleStep )
        {
            ResetEvent( m_hEvent );
            _AtlModule.PostMsgToMainThread( WM_SINGLESTEP );
            WaitForSingleObject( m_hEvent, INFINITE ); // wait for user action to continue execution
        }
    }

    if( getState() == XVM_Terminating )
        throw ERR_XVM_TERMINATE;    

    return XORT_OK;
}

// called after execution of single instruction before XVM advances IP to the next instruction
int CProgram::afterExecPrimitive( XRuntime *pRT, const DecoderData& primInfo, int iLastError )
{
    if( pRT->get_Flags() & XRTF_GOTO_EXECUTED )
    {
        TEXT_POSITION txtPos;
        txtPos.dwLine = m_pCurProc->GetLineFromIP( pRT->get_IP() );
        txtPos.dwColumn = 0;

        if( txtPos.dwLine != -1 ) 
        {
            CComQIPtr<IXDocument> spXDoc( m_spSourceFile );
            spXDoc->SetPosition( &txtPos );
        }
    }

    primInfo.m_strInstr.CopyToBuffer( m_szLastInstr );

    return XORT_OK;
}

int CProgram::beforeExecBuildinProc( XRuntime *pRT )
{
    return XORT_OK;
}

int CProgram::afterExecBuildinProc( XRuntime *pRT, int iLastError )
{
    return XORT_OK;
}

int CProgram::beforeExecNativeProc( XRuntime *pRT ) 
{
    return XORT_OK;
}

int CProgram::afterExecNativeProc( XRuntime *pRT, int iLastError )
{
    return XORT_OK;
}

int CProgram::internalXVMError( XRuntime *pRT, int iError )
{
    ATLTRACE( "CProgram::internalXVMError: %d\n", iError );
    
    TEXT_POSITION txtPos;
    CComQIPtr<IXDocument> spXDoc( m_spSourceFile );
    spXDoc->GetPosition( &txtPos );

    stringref pSrc = m_litPool.get( 2 );

    string csError;
    csError.Format( "\n<---!\nXVM INTERNAL ERROR : %d\nModule : '%s'\nSource : '%s'\nLine   : %u\nProc   : '%s'\nIP     : [0x%08X]\nInstruction : %s\nPlease contact development team.\n---!>\n", 
        iError, m_csXrtFileName.GetBuffer(), pSrc->GetBuffer(), txtPos.dwLine, m_pCurProc->m_csName.GetBuffer(), get_IP(), m_szLastInstr );

    outputDebugMessage( csError.GetBuffer() );

    m_pCurProc = NULL;
    return XORT_OK;
}

void CProgram::outputDebugMessage( LPCSTR lpszMessage )
{
    if( lpszMessage )
    {
        COutputStringEvent *pMsgEvent = new COutputStringEvent( lpszMessage );
        _AtlModule.PostMsgToMainThread( WM_OUTDBG_MESSAGE, (WPARAM)pMsgEvent );
    }
}

//////////////////////////////////////////////////////////////////////////
// called once when XVM enters new function
HRESULT CProgram::BuildStackFrame( CFrameInfo **ppOut )
{
    HRESULT hr;

    CFrameInfo *pFrameInfo = new CFrameInfo();
    pFrameInfo->AddRef();

    //////////////////////////////////////////////////////////////////////////
    // 1. Format function
    SymbolRef retType = FindType( m_listGlobal, m_pCurProc->m_dwType );
    pFrameInfo->m_bstrReturnType = CComBSTR( retType.m_csName.GetBuffer() ).Detach();
    pFrameInfo->m_dwValidFields |= FIF_RETURNTYPE;

    if( m_pCurProc->m_dwStorage == cSTORAGE_MEMBER ) {
        char szBuf[MAX_PATH];
        sprintf( szBuf, "%s::%s", m_pCurProc->m_pOwner->m_csName.GetBuffer(), m_pCurProc->m_csName.GetBuffer() );
        pFrameInfo->m_bstrFuncName = CComBSTR( szBuf ).Detach();
    } else {
        pFrameInfo->m_bstrFuncName = CComBSTR( m_pCurProc->m_csName.GetBuffer() ).Detach();
    }
    pFrameInfo->m_dwValidFields |= FIF_FUNCNAME;

    //////////////////////////////////////////////////////////////////////////
    // 2. virtual stack addresses:
    // XVM uses different stack implementation than hardware stack, so, take top
    // value, and sub XVM addresses from it;
    UINT64 addrMin = 0; //0x0006FFFF - dwStackBase;
    UINT64 addrMax = 0; //0x0006FFFF;
    pFrameInfo->m_addrMin = addrMin;
    pFrameInfo->m_addrMax = addrMax;
    //pFrameInfo->m_dwValidFields |= FIF_STACKRANGE;

    pFrameInfo->m_bstrLanguage = CComBSTR( XORT_LANG_NAME ).Detach();
    pFrameInfo->m_dwValidFields |= FIF_LANGUAGE;

    //////////////////////////////////////////////////////////////////////////
    // 3. Prepare stack frame info
    CComQIPtr<IDebugDocumentContext2> spDocCtx( m_spSourceFile );

    CComPtr<IDebugStackFrame2> spStackFrame;
    if( FAILED( hr = CStackFrame::Create( &spStackFrame ) ) )
        return hr;

    CComQIPtr<IXStackFrame> spXStkFrame( spStackFrame ); // get our internal interface

    spXStkFrame->Init( spDocCtx, this, get_StackBase(), addrMax, pFrameInfo );

    //////////////////////////////////////////////////////////////////////////
    // 4. Build local stack variables

    spXStkFrame->PushPropertyList(); // create new frame transition block

    // build function arguments if any
    DWORD dwNumArgs = m_pCurProc->GetArgumentsCount();
    if( dwNumArgs )
    {
        SYMBOL_LIST::iterator theIter = m_pCurProc->m_listMembers.end();
        theIter--; // skip local scope marker
        symbolref pSym = ( theIter == NULL ) ? NULL : (Symbol*)theIter;
        while( pSym )
        {
            CComPtr<IXProperty> spXProp;
            if( SUCCEEDED( hr = CDebugProperty::Create( &spXProp ) ) )
            {
                spXProp->Initialize( (IUnknown*)pSym, get_StackBase(), NULL, NULL, DBG_PROP_LOCALVAR );
                CComQIPtr<IDebugProperty2> spProp( spXProp );
                spXStkFrame->AddProperty( spProp );
            }

            theIter--;
            pSym = ( theIter == NULL ) ? NULL : (Symbol*)theIter;
        }
    }

    // check if this procedure is a member of some object, if so, add self pointer to stack!
    XCELLSARRAY& arithStack = get_Stack();
    XortElement& sysFrame = arithStack.get( get_StackBase() );
    if( sysFrame._ti != 0 )
    {
        CComPtr<IXProperty> spXProp;
        if( SUCCEEDED( hr = CDebugProperty::Create( &spXProp ) ) )
        {
            Symbol *pSymbol = NULL;
            xobject *pObject = (xobject *)sysFrame._ti;
            XObject *pUserObject = NULL;
            DWORD dwObjType = pObject->GetType();

            if( dwObjType >= t_system ) {
                pObject = NULL; // sorry guys, I can not show internals of the buil-in objects :)
            } else {
                pUserObject = (XObject *)pObject;
                pSymbol     = pUserObject->get_Symbol();
            }

            spXProp->Initialize( (IUnknown*)pSymbol, get_StackBase(), (IUnknown*)pUserObject, NULL, DBG_PROP_SELFPTR );
            CComQIPtr<IDebugProperty2> spProp( spXProp );
            spXStkFrame->AddProperty( spProp );
        }
    }

    // now build locals
    m_stackIter = m_pCurProc->m_stackMembers.begin(); // initialize stack transition iterator

    if( SUCCEEDED( hr = UpdateStackFrame( spXStkFrame ) ) )
    {
        pFrameInfo->m_pFrame = spStackFrame.Detach(); // NB! here is cross-reference between object and structure!
        pFrameInfo->m_dwValidFields |= FIF_FRAME;

        *ppOut = pFrameInfo;
    } else {
        pFrameInfo->Release();
    }

    return hr;
}

HRESULT CProgram::UpdateStackFrame( IXStackFrame *pStkFrame )
{
    HRESULT hr = S_OK;
    
    symbolref pSym = ( m_stackIter == NULL ) ? NULL : (Symbol*)m_stackIter;
    if( !pSym )
        return S_OK; // for build-in and native procedures there is not stack transition info...

    if( pSym->m_SymKind != sk_scopelocal ) 
    {
        // internal error!!!
        ATLTRACE( "CProgram::UpdateStackFrame() -> invalid frame marker!\n" );
        internalXVMError( this, ERR_DBG_INVALID_FRAMESCOPE );
        return E_FAIL;
    } else {
        m_stackIter++; // skip begin new frame marker
        pSym = ( m_stackIter == NULL ) ? NULL : (Symbol*)m_stackIter;
    }

    while( pSym && pSym->m_SymKind != sk_scopelocal ) // create properties till the next frameEnter marker
    {
        CComPtr<IXProperty> spXProp;
        if( SUCCEEDED( hr = CDebugProperty::Create( &spXProp ) ) )
        {
            spXProp->Initialize( (IUnknown*)pSym, get_StackBase(), NULL, NULL, DBG_PROP_LOCALVAR );
            CComQIPtr<IDebugProperty2> spProp( spXProp );
            pStkFrame->AddProperty( spProp );
        } else
            break;

        m_stackIter++;
        pSym = ( m_stackIter == NULL ) ? NULL : (Symbol*)m_stackIter;
    }

    // NB! after this while statement the m_stackIter will point to the next frameEnter or frameLeave marker
    return hr;
}

